package org.eclipse.emf.db.query;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.eclipse.emf.cdo.server.internal.db.CDODBSchema;
import org.eclipse.emf.db.DBObject;
import org.eclipse.emf.db.util.DBUtil;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.extensions.IDBSchema;

public abstract class QueryExpression {
    private static final SimpleDateFormat MYSQL_DATE_FORMAT=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //$NON-NLS-1$

    private static final String MARK=" ? "; //$NON-NLS-1$

    private QueryExpression() {
    }

    public String toString(IDBSchema schema) {
        return MARK;
    }

    @Override
    public final String toString() {
        return super.toString();
    }

    public Object[] getParameters() {
        List<Object> objects=new ArrayList<Object>();
        doAddParameters(objects);
        return objects.toArray();
    }

    protected void doAddParameters(List<Object> objects) {
    }

    public static QueryExpression adapt(DBObject obj) {
        return adapt(obj.cdoID());
    }

    public static QueryExpression adapt(final boolean b) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                objects.add(b);
            }
        };
    }

    public static QueryExpression adapt(final double d) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                objects.add(d);
            }
        };
    }

    public static QueryExpression adapt(final float f) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                objects.add(f);
            }
        };
    }

    public static QueryExpression adapt(final long l) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                objects.add(l);
            }
        };
    }

    public static QueryExpression adapt(final int i) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                objects.add(i);
            }
        };
    }

    public static QueryExpression adapt(final Enum<?> e) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                objects.add(e.ordinal());
            }
        };
    }

    public static QueryExpression adapt(final String str) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                objects.add(str);
            }
        };
    }

    public static QueryExpression adapt(final Date date) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                objects.add(MYSQL_DATE_FORMAT.format(date));
            }
        };
    }

    public static QueryExpression from(final EStructuralFeature feature) {
        return new QueryExpression() {
            @Override
            public String toString(IDBSchema schema) {
                return schema.getColumnName(feature);
            }
        };
    }

    public QueryExpression in(final SelectFinalStep step) {
        return new QueryExpression() {
            @Override
            public String toString(IDBSchema schema) {
                return QueryExpression.this.toString(schema) + " IN (" + step.getSQL() + ')';
            }
        };
    }

    public QueryExpression in(final DBObject... dbObjects) {
        return new QueryExpression() {
            @Override
            public String toString(IDBSchema schema) {
                StringBuffer buf=new StringBuffer();
                for (DBObject dbObject : dbObjects) {
                    if (buf.length() > 0)
                        buf.append(',');
                    // TODO: corriger la version PreparedStatement
                    // buf.append(adapt(dbObject));
                    buf.append(dbObject.cdoID());
                }
                return QueryExpression.this.toString(schema) + " IN (" + buf + ')';
            }
        };
    }

    public QueryExpression in(final Enum<?>... enumerations) {
        return new QueryExpression() {
            @Override
            public String toString(IDBSchema schema) {
                StringBuffer buf=new StringBuffer();
                for (Enum<?> enumeration : enumerations) {
                    if (buf.length() > 0)
                        buf.append(',');
                    buf.append(enumeration.ordinal());
                }
                return QueryExpression.this.toString(schema) + " IN (" + buf + ')';
            }
        };
    }

    public QueryExpression notIn(final SelectFinalStep step) {
        return new QueryExpression() {
            @Override
            public String toString(IDBSchema schema) {
                return QueryExpression.this.toString(schema) + " NOT IN (" + step.getSQL() + ')';
            }
        };
    }

    public static final QueryExpression ALL=new QueryExpression() {
        @Override
        public String toString(IDBSchema schema) {
            return "*"; //$NON-NLS-1$
        }
    };

    public static final QueryExpression CDO_ID=new QueryExpression() {
        @Override
        public String toString(IDBSchema schema) {
            return CDODBSchema.ATTRIBUTES_ID;
        }
    };

    public QueryExpression prefix(final EClass clazz) {
        return new QueryExpression() {
            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder(schema.getTableName(clazz)).append('.').append(QueryExpression.this.toString(schema)).toString();
            }
        };
    }

    public static QueryExpression from(final EReference ref) {
        return new QueryExpression() {
            @Override
            public String toString(IDBSchema schema) {
                return schema.getColumnName(ref);
            }
        };
    }

    public static QueryExpression type(final EReference ref, EClass clazz) {
        return new QueryExpression() {
            @Override
            public String toString(IDBSchema schema) {
                return DBUtil.getInternalClass(schema, ref);
            }
        }.eq(adapt(DBUtil.cdoInternalClass(clazz)));
    }

    public QueryExpression min() {
        return new QueryExpression() {
            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder("MIN(").append(QueryExpression.this.toString(schema)).append(')').toString(); //$NON-NLS-1$
            }
        };
    }

    public QueryExpression max() {
        return new QueryExpression() {
            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder("MAX(").append(QueryExpression.this.toString(schema)).append(')').toString(); //$NON-NLS-1$
            }
        };
    }

    public QueryExpression sum() {
        return new QueryExpression() {
            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder("SUM(").append(QueryExpression.this.toString(schema)).append(')').toString(); //$NON-NLS-1$
            }
        };
    }

    public QueryExpression not() {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                QueryExpression.this.doAddParameters(objects);
            }

            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder("NOT ").append(QueryExpression.this.toString(schema)).toString(); //$NON-NLS-1$
            }
        };
    }

    public QueryExpression and(final QueryExpression expr) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                QueryExpression.this.doAddParameters(objects);
                expr.doAddParameters(objects);
            }

            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder().append('(').append(QueryExpression.this.toString(schema))
                        .append(" AND ").append(expr.toString(schema)).append(')').toString(); //$NON-NLS-1$
            }
        };
    }

    public QueryExpression or(final QueryExpression expr) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                QueryExpression.this.doAddParameters(objects);
                expr.doAddParameters(objects);
            }

            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder().append('(').append(QueryExpression.this.toString(schema))
                        .append(" OR ").append(expr.toString(schema)).append(')').toString(); //$NON-NLS-1$
            }
        };
    }

    public QueryExpression lt(final QueryExpression expr) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                QueryExpression.this.doAddParameters(objects);
                expr.doAddParameters(objects);
            }

            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder().append('(').append(QueryExpression.this.toString(schema)).append('<').append(expr.toString(schema)).append(')')
                        .toString();
            }
        };
    }

    public QueryExpression gt(final QueryExpression expr) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                QueryExpression.this.doAddParameters(objects);
                expr.doAddParameters(objects);
            }

            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder().append('(').append(QueryExpression.this.toString(schema)).append('>').append(expr.toString(schema)).append(')')
                        .toString();
            }
        };
    }

    public QueryExpression eq(final QueryExpression expr) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                QueryExpression.this.doAddParameters(objects);
                expr.doAddParameters(objects);
            }

            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder().append('(').append(QueryExpression.this.toString(schema)).append('=').append(expr.toString(schema)).append(')')
                        .toString();
            }
        };
    }

    public QueryExpression like(final QueryExpression expr) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                QueryExpression.this.doAddParameters(objects);
                expr.doAddParameters(objects);
            }

            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder().append('(').append(QueryExpression.this.toString(schema)).append(" LIKE ").append(expr.toString(schema)).append(')')
                        .toString();
            }
        };
    }

    public QueryExpression le(final QueryExpression expr) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                QueryExpression.this.doAddParameters(objects);
                expr.doAddParameters(objects);
            }

            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder().append('(').append(QueryExpression.this.toString(schema))
                        .append("<=").append(expr.toString(schema)).append(')').toString(); //$NON-NLS-1$
            }
        };
    }

    public QueryExpression ge(final QueryExpression expr) {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                QueryExpression.this.doAddParameters(objects);
                expr.doAddParameters(objects);
            }

            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder().append('(').append(QueryExpression.this.toString(schema))
                        .append(">=").append(expr.toString(schema)).append(')').toString(); //$NON-NLS-1$
            }
        };
    }

    public QueryExpression isNull() {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                QueryExpression.this.doAddParameters(objects);
            }

            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder(QueryExpression.this.toString(schema)).append(" IS NULL ").toString(); //$NON-NLS-1$
            }
        };
    }

    public QueryExpression isNotNull() {
        return new QueryExpression() {
            @Override
            protected void doAddParameters(List<Object> objects) {
                QueryExpression.this.doAddParameters(objects);
            }

            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder(QueryExpression.this.toString(schema)).append(" IS NOT NULL ").toString(); //$NON-NLS-1$
            }
        };
    }

    private static final SimpleDateFormat SDF_DAY=new SimpleDateFormat("yyyy-MM-dd"); //$NON-NLS-1$

    private static final long MILLIS_PER_DAY=24L * 60L * 60L * 1000L;

    public QueryExpression between(final Date debut, Date fin) {
        final Date finalFin=new Date(fin.getTime() + MILLIS_PER_DAY);
        return new QueryExpression() {

            @Override
            protected void doAddParameters(List<Object> objects) {
                objects.add(SDF_DAY.format(debut));
                objects.add(SDF_DAY.format(finalFin));
            }

            @Override
            public String toString(IDBSchema schema) {
                return new StringBuilder(QueryExpression.this.toString(schema)).append(" BETWEEN ? AND ? ").toString(); //$NON-NLS-1$
            }
        };
    }
}
